package com.vains.config.bean;

import com.vains.client.AuthClient;
import com.vains.config.properties.Oauth2Properties;
import com.vains.io.JsonUtils;
import com.vains.system.model.ErrorTokenModel;
import com.vains.system.model.Result;
import com.vains.utils.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;

/**
 * 自定义远程验证类
 * @author vains
 */
@Slf4j
@Primary
@Component
public class CustomerRemoteTokenServices extends RemoteTokenServices {

    private final AuthClient authClient;

    private final Oauth2Properties oauth2Properties;

    private AccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();

    /*@Value("${security.oauth2.client.client-id}")
    private String clientId;

    @Value("${security.oauth2.client.client-secret}")
    private String clientSecret;

    @Value("${security.oauth2.resource.token-info-uri}")
    private String checkTokenEndpointUrl;*/

    @Override
    public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
        String authorizationHeader = getAuthorizationHeader(oauth2Properties.getClient().getClientId(), oauth2Properties.getClient().getClientSecret());
        // Map<String, Object> map = postForMap(oauth2Properties.getResource().getTokenInfoUri(), formData, headers);
        HttpServletRequest request = SpringContextUtils.getCurrentRequest();
        request.setAttribute("authorizationHeader", authorizationHeader);
        Map<String, Object> map = authClient.checkToken(accessToken);
        String json = JsonUtils.objectCovertToJson(map);
        Result<?> result = JsonUtils.jsonCovertToObject(json, Result.class);

        // 如果是 统一响应对象, 则判断是否正确
        if (result != null && result.getSuccess() != null && !result.getSuccess()) {
            ErrorTokenModel errorToken = new ErrorTokenModel(result.getMessage(), accessToken, null);
            String errorInfo = JsonUtils.objectCovertToJson(errorToken);
            throw new InvalidTokenException(errorInfo);
        }

        if (map.containsKey("error")) {
            if (logger.isDebugEnabled()) {
                logger.debug("check_token returned error: " + map.get("error"));
            }
            ErrorTokenModel errorToken = new ErrorTokenModel((String) map.get("error_description"), accessToken, (String) map.get("error"));
            String errorInfo = JsonUtils.objectCovertToJson(errorToken);
            throw new InvalidTokenException(errorInfo);
        }

        // gh-838
        if (!Boolean.TRUE.equals(map.get("active"))) {
            logger.debug("check_token returned active attribute: " + map.get("active"));
            ErrorTokenModel errorToken = new ErrorTokenModel((String) map.get("active"), accessToken, (String) map.get("error"));
            String errorInfo = JsonUtils.objectCovertToJson(errorToken);
            throw new InvalidTokenException(errorInfo);
        }

        return tokenConverter.extractAuthentication(map);
    }

    private String getAuthorizationHeader(String clientId, String clientSecret) {

        if(clientId == null || clientSecret == null) {
            logger.warn("Null Client ID or Client Secret detected. Endpoint that requires authentication will reject request with 401 error.");
        }

        String creds = String.format("%s:%s", clientId, clientSecret);
        return "Basic " + new String(Base64.getEncoder().encode(creds.getBytes(StandardCharsets.UTF_8)));
    }

    public CustomerRemoteTokenServices(AuthClient authClient, Oauth2Properties oauth2Properties) {
        this.authClient = authClient;
        this.oauth2Properties = oauth2Properties;
    }

}
